$(function()
{
////////////////////////////////////////////////////////////////////////////////
//
// グローバル
//
////////////////////////////////////////////////////////////////////////////////
/**
 * 変数
 */
var g_scale = 1;
var g_color = "";
var g_idea_map = {};
var g_selected_id = new Array();
var g_canvas_layer = $("#canvas_layer");
var g_scroll_layer = $("#scroll_layer");
var g_view_layer = $("#view_layer");

/**
 * 現在のスケールを取得する。
 * @return スケール(0～1) 
 */
function getGlobalScale()
{
	return g_scale;
}

/**
 * クライアントカラーを取得する。
 * @return 色
 */
function getGlobalColor()
{
	if (g_color == "")
	{
		// クライアントの色を決める（ランダム）
		var color_rgb = [];
		color_rgb[0] = Math.floor(Math.random() * 127); 
		color_rgb[1] = Math.floor(Math.random() * 127); 
		color_rgb[2] = Math.floor(Math.random() * 127);
		 
		// パステルカラーにする
		color_rgb[Math.floor(Math.random() * 3)] = 255;
		
		// RGBカラーの結合
		g_color = color_rgb[0] + "," + color_rgb[1] + "," + color_rgb[2];
	}
	return g_color;
}
////////////////////////////////////////////////////////////////////////////////
//
// クラス
//
////////////////////////////////////////////////////////////////////////////////
/**
 * アイデアクラス
 */
var Idea = function(id, comment, image, color, size, parent_id)
{
	// ID
	this.id = id;
	// 親ID
	this.parent_id = (parent_id)? parent_id : "";
	// コメント
	this.comment = comment;
	// イメージ
	this.image = image;
	// 子のIDリスト
	this.children = new Array();
	// 背景色
	this.color = color;
	// サイズ
	this.size = size;
	// 位置(y)
	this.top = 
		Math.floor(Math.random() * 
		(g_view_layer.height() - size)) - g_canvas_layer.offset().top;
	// 位置(x)
	this.left = 
		Math.floor(Math.random() * 
		(g_view_layer.width() - size)) - g_canvas_layer.offset().left;
	
	
	/**
	 * 子を追加する。
	 * @param id 子のID 
	 */
	this.addChild = function(id)
	{
		this.children.push(id);
	}
	
	/**
	 * 子を削除する。
	 * @param 子のID
	 */
	this.removeChild = function(id)
	{
		this.children.pop(id);
	}
	
	/**
	 * 親IDを取得する。
	 * @return 親ID 
	 */
	this.getParentId = function()
	{
		return this.parent_id;
	}
	
	/**
	 * 親子関係を持たないアイデアであるかどうかを取得する。
	 */
	this.isAlone = function()
	{
		if (this.parent_id == "" && this.children.length == 0)
		{
			return true;
		}
		return false;
	}
	
	/**
	 * 直径を計算する。
	 * @return 直径 
	 */
	this.calcSize = function()
	{
		return Math.floor( (Math.floor(this.children.length / 3) * 15 + 100) * getGlobalScale());
	}
	
	/**
	 * 文字サイズを計算する。
	 * @return 文字サイズ 
	 */
	this.calcFontSize = function()
	{
		return Math.floor(16 * getGlobalScale());
	}
	
	/**
	 * 選択された場合の動作を規定する。
	 */
	this.select = function()
	{
		// 選択状態を規定したCSSを適用
		$("#" + this.id).css("-webkit-animation-name", "fuyopika_motion");
	}
	
	/**
	 * 選択解除された場合の動作を規定する。
	 */
	this.deselect = function()
	{
		// 選択解除状態を規定したCSSを適用
		$("#" + this.id).css("-webkit-animation-name", "fuyo_motion");
	}
	
	/**
	 * 再描画を行う。
	 */
	this.invalidate = function()
	{
		// アイデアを取得
		var idea = $("#" + this.id);
		// サイズを取得
		var parentSize = this.calcSize();
		// 文字サイズを取得
		var tmp_font_size = this.calcFontSize();
		// 子アイデアの描画角度を算出
		var deg = 360 / this.children.length;
		
		// 子アイデアの表示位置を更新
		for (var i = 0; i<this.children.length; i++)
		{
			var childSize = g_idea_map[String(this.children[i])].calcSize();
			
			// 子の位置を計算
			var distant = Math.floor((parentSize + childSize) / 2);
			var diff = Math.floor((parentSize - childSize) / 2);
			var x = Math.floor(this.left + diff + distant * Math.cos(deg * i * Math.PI / 180));
			var y = Math.floor(this.top + diff + distant * Math.sin(deg * i * Math.PI / 180));
			
			g_idea_map[String(this.children[i])].top = y;
			g_idea_map[String(this.children[i])].left = x;
		}
		
		// 自分の表示位置を更新
		idea.parent().css({
			"top" : this.top + "px",
			"left" : this.left + "px"
		});
		idea.css({
			"font-size" : tmp_font_size + "px",
			"width" : parentSize + "px",
			"height" : parentSize + "px",
			"border-radius" : Math.floor(parentSize)/2 + "px",
			"-moz-border-radius" : Math.floor(parentSize)/2 + "px",
			"-webkit-border-radius" : Math.floor(parentSize)/2 + "px",
			"background-image" : "-webkit-gradient(radial, center center, 0, center center, " + parentSize * 0.75 + ", from(rgba(" + this.color + ", 1)), to(rgba(255, 255, 255, 1)))",
		});
		
		// 子要素の再描画を行う
		for (var i = 0; i < this.children.length; i++)
		{
			g_idea_map[String(this.children[i])].invalidate();
		}
	}
	
	/**
	 * タグオブジェクトとして取得する。
	 */
	this.toTag = function()
	{
		// サイズ情報を取得
		var tmp_size = this.calcSize();
		var tmp_font_size = this.calcFontSize();
		
		// アイデアのコンテナを生成
		var container = $("<div>")
			.addClass("idea_container")
			.css({
				"top" : this.top + "px",
				"left" : this.left + "px"
			});
		
		// アイデアそのものを生成
		var idea = $("<div>");
		
		if (this.comment) {
			idea.text(this.comment);
		}
		else if (this.image) {
			idea.append(this.image);
		}
		
		idea
			.attr("id", this.id)
			.addClass("idea")
			
			.css({
				"font-size" : tmp_font_size + "px",
				"width" : tmp_size + "px",
				"height" : tmp_size + "px",
				"border-radius" : Math.floor(tmp_size)/2 + "px",
				"-moz-border-radius" : Math.floor(tmp_size)/2 + "px",
				"-webkit-border-radius" : Math.floor(tmp_size)/2 + "px",
				"background" : "-moz-radial-gradient( cover, rgba(" + this.color + ",1), rgba(255, 255, 255, 1))",
				"background-image" : "-webkit-gradient(radial, center center, 0, center center, " + tmp_size * 0.75 + ", from(rgba(" + this.color + ", 1)), to(rgba(255, 255, 255, 1)))",
				"border" : "1px solid rgba(" + this.color + ", 0.7)"
			})
			.appendTo(container);
		
		return container;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// アイデア描画関数
//
////////////////////////////////////////////////////////////////////////////////
/**
 * 初期描画を行う。
 */
function initDraw()
{
	for (var i in g_idea_map)
	{
		newDraw(g_idea_map[i]);
	}
}

/**
 * 新規アイデアの描画を行う。
 * @param 描画対象のアイデアオブジェクト
 */
function newDraw(targetIdea)
{
	targetIdea.toTag().appendTo(g_canvas_layer)
	.draggable({
		containment: "body",
		opacity: 0.5,
		cursor: 'move',
		zIndex: 10000,
		stop: function(e, ui)
		{
			var ideaObj = g_idea_map[String($(this).children(".idea").attr("id"))];
			ideaObj.top = ui.position.top;
			ideaObj.left = ui.position.left;
			refreshDraw(ideaObj);
		}
	})
	.droppable({
		containment: "body",
		zIndex: 10000,
		drop: function(e, ui)
		{
			// 受容体オブジェクト情報
			var receptorObj = g_idea_map[String($(this).children(".idea").attr("id"))];
			// D&Dされたオブジェクトの情報
			var senderObj = g_idea_map[String($(e.toElement).attr("id"))];
			
			// 孤立アイデアに限り、受容体の傘下に収める
			if (senderObj && senderObj.isAlone())
			{
				// 親子関係を設定
				receptorObj.addChild(senderObj.id);
				senderObj.parent_id = receptorObj.id;

				// 親子関係を送信
				sendLinkage(senderObj.id, receptorObj.id);
			}
		}
	});
}

/**
 * アイデア一族の再描画を行う。
 * @param 描画対象のアイデアオブジェクト 
 */
function refreshDraw(targetIdea)
{
	// 祖先を検索する
	var tmpIdea = targetIdea;
	while (tmpIdea.getParentId() != "")
	{
		tmpIdea = g_idea_map[String(tmpIdea.getParentId())];
	}
	
	tmpIdea.invalidate();
} 

/**
 * メッセージオブジェクトを処理する
 * @param {object} obj メッセージオブジェクト
 */
function message(obj)
{
	// 通知メッセージの場合
	if ("announcement" in obj)
	{
		// チャット表示領域の更新
		$("<p>").append($("<em>").text(esc(obj.announcement))).appendTo($("div#chat"));
	}
	// 関連付け情報の場合
	else if ("linkage" in obj)
	{
		// 親子関係を設定
		var ideaObj = g_idea_map[String(obj.linkage[0])];
		g_idea_map[String(obj.linkage[1])].addChild(obj.linkage[0]);
		ideaObj.parent_id = obj.linkage[1];

		// オブジェクトの位置を調整
		refreshDraw(ideaObj);
	}
	// テキスト情報の場合
	else if ("message" in obj)
	{
		// チャット表示領域の更新
		$("<p>").append($("<b>").text(esc(obj.message[0]) + ":")).append(esc(obj.message[2])).appendTo($("div#chat"));
		
		// メッセージの情報を取得
		var id = obj.message[1];
		var txt = obj.message[2];
		var color = obj.message[3];
		var parentId = obj.message[4];
		var size = 100; // サーバーからわたってこないので固定値
		
		// アイデア生成
		var newIdea = new Idea(id, txt, null, color, size, parentId);
		// マップへ追加
		g_idea_map[String(id)] = newIdea;
		
		if (parentId && g_idea_map[String(parentId)]) // TODO 「&&」の後は暫定コードである、サーバーから全データが送信されるべき
		{
			// 親へ子の追加を通知
			g_idea_map[String(parentId)].addChild(id);
		}
		
		// 新アイデアを描画
		newDraw(newIdea);
		
		if (parentId)
		{
			// 系列アイデアを再描画
			refreshDraw(newIdea);
		}
		
		// TODO:要仕様検討
		//transparent(obj.message[1]);
	}
	// イメージ情報の場合
	else if ("image" in obj)
	{
		// チャット表示領域の更新
		$("<p>").append($("<b>").text(esc(obj.image[0]) + ":")).append(esc("[イメージが追加されました]")).appendTo($("div#chat"));
		
		// メッセージの情報を取得
		var id = obj.image[1];
		var img = $("<img>")
			.addClass("idea_image")
			.attr("src", obj.image[2]);
		var color = obj.image[3];
		var parentId = obj.image[4];
		var size = 100; // サーバーからわたってこないので固定値

		// アイデア生成
		var newIdea = new Idea(id, null, img, color, size, parentId);
		// マップへ追加
		g_idea_map[String(id)] = newIdea;
		
		if (parentId && g_idea_map[String(parentId)]) // TODO 「&&」の後は暫定コードである、サーバーから全データが送信されるべき
		{
			// 親へ子の追加を通知
			g_idea_map[String(parentId)].addChild(id);
		}
		
		// 新アイデアを描画
		newDraw(newIdea);
		
		if (parentId)
		{
			// 系列アイデアを再描画
			refreshDraw(newIdea);
		}
		
		// TODO:要仕様検討
		//transparent(obj.message[1]);
	}
	$("div#chat").attr("scrollTop", "1000000");
}

////////////////////////////////////////////////////////////////////////////////
//
// サーバ連携関数
//
////////////////////////////////////////////////////////////////////////////////
/**
 * WebSocketでアイデアのテキスト情報を送信する。
 */
function sendText()
{
	var text = $("input#text").val();
	var parent_id = g_selected_id.length > 0 ? g_selected_id[0] : undefined;
	
	$("input#text").val("");
	socket.send({ message: [text, getGlobalColor(), parent_id] });
}
	
/**
 * WebSocketでアイデアの関連付け情報を送信する。
 * @param {string} obj_id オブジェクトID
 * @param {string} parent_id 親のオブジェクトID
 */
function sendLinkage(obj_id, parent_id)
{
	socket.send({ linkage: [obj_id, parent_id] });
}
	
/**
 * WebSocketでアイデアのイメージ情報を送信する。
 * @param {object} dataURL_file DataURLファイル
 */
function sendImage(dataURL_file) 
{
	var parent_id = g_selected_id.length > 0 ? g_selected_id[0] : undefined;
	
	socket.send({image: [dataURL_file, getGlobalColor(), parent_id]});
}


////////////////////////////////////////////////////////////////////////////////
//
// 汎用関数
//
////////////////////////////////////////////////////////////////////////////////
/**
 * エスケープを行う。
 * @param string msg 対象文字列
 * @return 
 */
function esc(msg)
{
	return msg.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

////////////////////////////////////////////////////////////////////////////////
//
// イベントハンドラー
//
////////////////////////////////////////////////////////////////////////////////
var dragDelta = {
	top: g_view_layer.offset().top +
		 g_canvas_layer.offset().top,
	left: g_view_layer.offset().left +
		  g_canvas_layer.offset().left,
	};

// Ctrlキーを押下した際、画面スクロールモードになる
$(window).keydown(function(e) {
	if (e.keyCode == 17) {
		var view_layer = g_view_layer;
		g_scroll_layer
			.css({
				"left": view_layer.css("left"),
				"top": view_layer.css("top"),
				"width": view_layer.width(),
				"height": view_layer.height(),
				"z-index": 9999,
				"display": ""
			});
		
		g_scroll_layer.draggable({
			helper: function() {
				return $("<div />");
			},
			start: function(e, ui) {
				var offset = g_canvas_layer.offset();
				g_canvas_layer
					.data("top0", offset.top)
					.data("left0", offset.left);
			},
			drag: function(e, ui) {
				// キャンバスレイヤーの画面上の位置を取得
				var canvas_layer = g_canvas_layer;
				var offset = canvas_layer.offset();
				
				var dragTop = ui.position.top + canvas_layer.data("top0") - dragDelta.top;
				var dragLeft = ui.position.left + canvas_layer.data("left0") - dragDelta.left;
				
				g_canvas_layer
					.css("top", dragTop).css("bottom", -dragTop)
					.css("left", dragLeft).css("right", -dragLeft)
			}
		});
	}
});

// Ctrlキーを離した際、画面スクロールモードが終了する
$(window).keyup(function(e) {
	if (e.keyCode == 17) {
		g_scroll_layer
			.css({
				"z-index": -1,
				"display": "none"
				});
	}
});

function rescale(selector, property, percent) {
	var oldValue = parseFloat($(selector).css(property));
	$(selector).css(property, oldValue*percent);
}

function rescaleRelative(selector, property, relative, percent) {
	var oldValue = parseFloat($(selector).css(property)) - relative;
	$(selector).css(property, oldValue*percent + relative);		
}

$('#view_layer').mousewheel(function(event, delta) {
 
	var containerTopOffset = parseFloat(g_view_layer.css("top"));
	var containerLeftOffset = parseFloat(g_view_layer.css("left"));
	var scale = 0.1;
	var percent = 1 + delta*scale;		
	$(".idea").each(function() {
		g_scale = g_scale + (delta * scale);
		refreshDraw(g_idea_map[String($(this).attr("id"))]);
	});
	event.preventDefault();
});

$("#input_form").draggable({
	opacity: 0.5,
	cursor: "move"
});

$("#chat").draggable({
	opacity: 0.5,
	cursor: "move"
});

/**
 * 送信ボタンのクリックイベントハンドラー
 */
$("input#send_msg").click(function ()
{
	// 空文字は送信しない
	if ($("input#text").val()) {
		sendText();
	}
	// テキストボックスにフォーカスする
	$("input#text").focus();
});

/**
 * テキストボックスのキーダウンハンドラー
 */
$("input#text").keydown(function(e)
{
	if (e.keyCode == 13)
	{
		$("input#send_msg").click();
	}
});

/**
 * アイデアのクリックハンドラー
 */
$("div.idea").live("click", function()
{
	// クリックされたアイデアのIDを取得
	var thisId = $(this).attr("id");
	
	// 現在選択されているアイデアのIDを取得
	var prevId = g_selected_id.pop();
	
	if (prevId)
	{
		// 選択解除
		g_idea_map[String(prevId)].deselect();
	}
	
	// 異なるアイデアをクリックした場合のみ
	if (prevId != thisId)
	{
		// 選択
		g_idea_map[String(thisId)].select();
		g_selected_id.push(thisId);
	}
	$("input#text").focus();
});

/**
 * アイデアのホバーイベントハンドラー
 */
$("div.idea:parent").live("hover", function(e)
{
	// マウスオーバー時に最前面にする
	switch (e.type) {
		case "mouseenter":
			$(this).css("z-index", 10000);
			$(this).css("border-width", "3px");
			break;
		case "mouseleave":
			$(this).css("z-index", 0);
			$(this).css("border-width", "1px");
			break;
		default:
			break;
	}
});

////////////////////////////////////////////////////////////////////////////////
//
// ファイルドラッグ
//
////////////////////////////////////////////////////////////////////////////////
$("#send_msg")
	.bind("dragenter", function()
	{
		$(this).addClass("drag_over"); 
		return false;
	})
	.bind("dragleave", function()
	{
		$(this).removeClass("drag_over"); 
		return false;
	})
	.bind("dragover", function(e)
	{
		e.preventDefault(); 
		return false;
	})
	.bind("drop", function(e)
	{
		e.preventDefault();

		// ドロップされたファイルのリストを取得
		var files = e.originalEvent.dataTransfer.files;
		
		if (files.length > 0) {
			var file = files[0];
			if (!/^image/.test(file.type))
			{
				alert('イメージファイルをドロップしてください。');
				return false;
			}
			
			// ファイル読み取りオブジェクト生成
			var reader = new FileReader();
			reader.readAsDataURL(file);
			
			// ファイル読み込み正常終了イベントリスナ
			reader.onload = function(e)
			{
				sendImage(reader.result);
			};
		}
		return false;
	});

////////////////////////////////////////////////////////////////////////////////
//
// メイン処理
//
////////////////////////////////////////////////////////////////////////////////
// テキストボックスにフォーカスする
$("input#text").focus();

// サーバに接続する
var socket = new io.Socket(null, {port: 80, rememberTransport: false});
socket.connect();
	
// サーバーからのメッセージ処理ハンドラを定義
socket.on("message", function(obj)
{
	if ("buffer" in obj)
	{
		$("div#chat").text("");
	  
		for (var i in obj.buffer)
		{
			message(obj.buffer[i]);
		}
	}
	else
	{
		message(obj);
	}
});

}) // end of $(function())

